/** \file gu_material.h */
#pragma once

#include "gu_shader_variable.h"
#include "gr_numeric_types.h"
#include "gu_uniform_map.h"

#include "gr_shims.h"
#include<string>
#include<memory>
#include<cstdint>

namespace gu {

class Shader;



/* polymorphics vars */

class MaterialVariable : public Variable{
public:
    typedef std::shared_ptr<MaterialVariable> shared_ptr;


    ShaderLoc location_;
    bool enabled; /** Enabled can be used to overload transfer values from env */

    MaterialVariable(const Variable& signature, ShaderLoc location):location_(location),Variable(signature), enabled(true)
    {}

    ShaderLoc& location(){return location_;}

    float* scalar(){assert(!"Cannot apply basetype");return nullptr;}
    gr::vec3* vec3(){assert(!"Cannot apply basetype");return nullptr;}
    gr::vec4* vec4(){assert(!"Cannot apply basetype");return nullptr;}
    gr::mat4* mat4(){assert(!"Cannot apply basetype");return nullptr;}

    void set(float s){assert(!"Cannot apply basetype");}
    void set(gr::vec3& v){assert(!"Cannot apply basetype");}
    void set(gr::vec4& v){assert(!"Cannot apply basetype");}
    void set(gr::mat4& m){assert(!"Cannot apply basetype");}
    void apply(UniformMap* umap){assert(!"Cannot apply basetype");}
};

class StreamSlot : public Variable
{
    ShaderLoc location_;
public:
    typedef std::shared_ptr<StreamSlot> shared_ptr;

    ShaderLoc& location(){return location_;}

    StreamSlot(const Variable& signature, ShaderLoc location):Variable(signature)
    {
        location_ = location;
    }

};

// TODO: Move these typedefs somewhere else
typedef std::map<std::string, MaterialVariable::shared_ptr> MaterialVarMap;
typedef std::map<std::string, StreamSlot::shared_ptr> StreamSlotMap;
typedef std::map<std::string, ShaderLoc> LocMap;

/**
Material replicates uniform values in shader and applies default value for them. Env can overload material parameters.
*/
class MaterialImp;
class Material
{
public:
    typedef std::shared_ptr<Material> shared_ptr;

    MaterialImp* impl_;

    Material();
    ~Material();
    Shader* shader();

    MaterialVarMap& uniforms();
    StreamSlotMap&  inputStreams();

    LocMap& uniformLocMap();
    LocMap& inputStreamLocMap();

    /** Transform uniform data through uniform map. */
    void use(UniformMap* umap);
};

Material* newGlMaterial(const char* vertexShader, const char* fragmentShader);

}
    